<template>
  <div class="history-container">
    <div class="top-bar">
      <h1 class="app-title">上传记录档案</h1>
      <div class="action-container">
        <div class="search-group">
          <el-input
              v-model="searchName"
              placeholder="按账号搜索"
              clearable
              class="search-input"
              @keyup.enter="handleSearch"
          >
            <template #prefix>
              <el-icon><Search /></el-icon>
            </template>
          </el-input>
          <div class="number-range">
            <el-input-number
                v-model="minTotal"
                :min="0"
                placeholder="最小数量"
                controls-position="right"
                class="number-input"
            />张
            <span class="separator">——</span>
            <el-input-number
                v-model="maxTotal"
                :min="minTotal"
                placeholder="最大数量"
                controls-position="right"
                class="number-input"
            />张
          </div>
          <el-date-picker
              v-model="dateRange"
              type="daterange"
              range-separator="——"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              value-format="YYYY-MM-DD"
              class="date-picker"
          />

        </div>

        <div class="action-buttons">
          <el-tooltip content="返回" placement="bottom">
            <el-button
                type="info"
                size="large"
                circle
                plain
                @click="handleReturn"
                class="return-button"
            >
              <el-icon :size="32" style="color: dodgerblue"><Back /></el-icon>
            </el-button>
          </el-tooltip>

          <el-tooltip content="上传图片（可多选）" placement="bottom">
            <el-button
                type="primary"
                circle
                plain
                @click="handleUploadClick"
                class="flat-button"
            >
              <el-icon :size="32" style="color: white"><Upload /></el-icon>
            </el-button>
          </el-tooltip>
          <input
              ref="fileInput"
              type="file"
              hidden
              accept="image/jpeg, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
              multiple
              @change="handleFileSelect"
          >
          <input
              ref="excelInput"
              type="file"
              hidden
              accept=".xlsx, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
              @change="handleExcelSelect"
          >
          <input
            ref="imageInput"
            type="file"
            hidden
            accept="image/jpeg"
            multiple
            @change="handleImageSelect"
          >
        </div>
      </div>
    </div>
    <el-dialog
    v-model="typeDialogVisible"
    title="选择上传类型"
    width="500px"
    class="type-dialog"
    center
  >
    <div class="type-selector">
      <div
        v-for="type in uploadTypes"
        :key="type.value"
        class="type-card"
        :class="{ 'active-type': selectedType === type.value }"
        @click="selectedType = type.value"
      >
        <el-icon :size="48" :color="selectedType === type.value ? '#409EFF' : '#909399'">
          <component :is="type.icon" />
        </el-icon>
        <h3 class="type-title">{{ type.label }}</h3>
        <p class="type-desc">{{ type.description }}</p>
      </div>
    </div>
    <template #footer>
      <el-button
        type="primary"
        :disabled="!selectedType"
        @click="handleTypeConfirm"
      >
        确认选择
      </el-button>
    </template>
  </el-dialog>
    <div class="container">
       <div class="filter-buttons">
      <el-button
        v-for="type in filterTypes"
        :key="type.value"
        :class="['type-btn', { active: selectedTypeFilter === type.value }]"
        @click="selectedTypeFilter = type.value"
      >
        {{ type.label }}
      </el-button>
    </div>
      <el-table
          :data="filteredData"
          style="width: 100%"
          class="history-table"
          border:true
          height=70vh
          stripe
          v-loading="loading"
          @row-click="handleRowClick"
      >
        <el-table-column

            prop="type"
            label="类型"
            width="120"
            align="center"
            header-align="center"
        >
          <template #default="{ row }">
            <el-tag :type="typeTagStyle(row.type)" effect="dark">
              {{ row.type }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column
            prop="username"
            label="账号"
            width="144"
            align="center"
            header-align="center"
        />
        <el-table-column
            prop="uploadTime"
            label="上传时间"
            width="220"
            align="center"
            header-align="center"
        />
        <el-table-column
            prop="total"
            label="上传总数"
            width="130"
            align="center"
            header-align="center"
        >
          <template #default="{ row }">
            <span >{{ row.total }}</span>
          </template>
        </el-table-column>
        <el-table-column
            prop="validPairs"
            label="有效数量"
            width="130"
            align="center"
            header-align="center"
        >
          <template #default="{ row }">
            <el-tag type="success" effect="dark">
              {{ row.type === 'MCC' ? `${row.validPairs} 对` : `${row.total} 张` }}
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" width="120" align="center" header-align="center">
          <template #default="{ row }">
            <el-button
                type="primary"
                size="large"
                @click.stop="showDetail(row)"
                class="detail-btn"
            >
              详情
            </el-button>
          </template>
        </el-table-column>
      </el-table>

      <!-- 详情对话框 -->
      <el-dialog
          v-model="dialogVisible"
          :title="`上传详情 - ${currentUser}`"
          width="60%"
          class="detail-dialog"
      >
        <el-table :data="detailData" border style="width: 100%">
          <el-table-column
              prop="pairNumber"
              label="配对编号"
              width="120"
              align="center"
              header-align="center"
          />
          <el-table-column label="图片ID" align="center" header-align="center">
            <el-table-column
                prop="leftId"
                label="左图ID"
                width="160"
                align="center"
                header-align="center"
            />
            <el-table-column
                prop="rightId"
                label="右图ID"
                width="160"
                align="center"
                header-align="center"
            />
          </el-table-column>
          <el-table-column
              prop="status"
              label="状态"
              width="120"
              align="center"
              header-align="center"
          >
            <template #default="{ row }">
              <el-tag
                  :type="statusTagType(row.status)"
                  effect="light"
                  class="status-tag"
              >
                {{ row.status }}
              </el-tag>
            </template>
          </el-table-column>
        </el-table>
      </el-dialog>
    </div>
  </div>
</template>

<script setup lang="ts">
import { ref, onMounted, computed } from 'vue'
import {ElLoading, ElMessage, ElMessageBox, UploadFile, UploadFiles} from 'element-plus'
import { request } from "/src/utils/service"
import * as XLSX from "xlsx";
import { useRoute, useRouter } from 'vue-router';
import {Close, Message,ArrowLeft,Search, Document} from '@element-plus/icons-vue'
interface ImageRecord {
  id: number
  user:number,
  image_url: string
  create_datetime: string
  type: 'MCC' | 'DR' | 'OCT'
  // 可根据实际API响应添加其他字段
}

interface ProcessedItem {
  type: 'MCC' | 'DR' | 'OCT' // 新增类型字段
  user:number
  dirPath: string
  username: string
  uploadTime: string
  total: number
  validPairs: number
  pairDetails: PairDetail[]
}
interface UserInfo {
  role:string;
  name: string;
  identification: string;
  id: number;
  username:string;
}
interface PairDetail {
  pairNumber: string
  leftId: number | string
  rightId: number | string
  status: '已配对' | '左图缺失' | '右图缺失' | '单张图片'
}

const router = useRouter()
// 响应式数据
const loading = ref<boolean>(false)
const dialogVisible = ref<boolean>(false)
const currentUser = ref<string>('')
const rawData = ref<ImageRecord[]>([])
const processedData = ref<ProcessedItem[]>([])
const detailData = ref<PairDetail[]>([])
const activeIndex = ref(-1);
const apiPrefix = '/api/images/';
const fileInput = ref<HTMLInputElement>();
const imageInput = ref<HTMLInputElement>();
const tableLoading = ref(false);
const deleteLoading = ref<Record<number, boolean>>({});
const selectedImage = ref('');
const excelInput = ref<HTMLInputElement>();
const excelPromiseResolve = ref<((file: File) => void) | null>(null);
const excelPromiseReject = ref<((reason?: any) => void) | null>(null);
const searchId = ref('')
const searchName = ref<string>('')
const dateRange = ref<string[]>([])
const minTotal = ref<number | null>(null)
const maxTotal = ref<number | null>(null)
const userInfo = ref<UserInfo>();
const typeDialogVisible = ref(false);
const selectedType = ref<'MCC' | 'DR' | 'OCT' | null>(null);
const selectedTypeFilter = ref<'all' | 'MCC' | 'DR' | 'OCT'>('all')
const filterTypes = [
  { value: 'all', label: '全部' },
  { value: 'MCC', label: 'MCC' },
  { value: 'DR', label: 'DR' },
  { value: 'OCT', label: 'OCT' }
]
const uploadTypes = [
  {
    value: 'MCC',
    label: '彩色眼底图像疾病多标签',
    icon: 'Picture',
    description: '上传彩色眼底图像',
    api: '/api/images/',
    info_api:'/mcc_history'
  },
  {
    value: 'DR',
    label: '糖尿病视网膜病变诊断',
    icon: 'Camera',
    description: '上传糖尿病视网膜病变筛查图像',
    api: '/api/dr/',
    info_api:'/dr_history'
  },
  {
    value: 'OCT',
    label: '光学相干断层扫描诊断',
    icon: 'VideoCamera',
    description: '上传OCT扫描图像',
    api: '/api/oct/',
    info_api:'/oct_history'
  }
];
// 计算属性
const filteredData = computed<ProcessedItem[]>(() => {
  const isAdmin = userInfo.value?.role === 'admin';
  const currentUsername = userInfo.value?.username || '';
  return processedData.value.filter(item => {
    let userMatch = true;
    if (!isAdmin) {
      userMatch = item.username === currentUsername;
    }
    const nameMatch = item.username.toLowerCase().includes(searchName.value.toLowerCase())
     const typeMatch = selectedTypeFilter.value === 'all' || item.type === selectedTypeFilter.value
    // 日期范围过滤
    let dateMatch = true
    if (dateRange.value?.length === 2) {
      const [start, end] = dateRange.value
      const itemDate = item.uploadTime.split(' ')[0]
      dateMatch = itemDate >= start && itemDate <= end
    }

    // 数量范围过滤
    const total = item.total
    const min = minTotal.value ?? -Infinity
    const max = maxTotal.value ?? Infinity
    const totalMatch = total >= min && total <= max

    return nameMatch && dateMatch && totalMatch&&userMatch&&typeMatch
  })
   .sort((a, b) => {
      return new Date(b.uploadTime).getTime() - new Date(a.uploadTime).getTime()
    })
})

// 搜索方法保持不变
const handleSearch = () => {

}

// 生命周期
onMounted(async () => {
  try {
    loading.value = true;
    await loadData();
    userInfo.value = await fetchUserInfo(); // 获取用户信息
  } finally {
    loading.value = false;
  }
})
// 方法
const loadData = async () => {
  try {
    loading.value = true
    // 并行请求三个接口
    const [mccRes, drRes, octRes] = await Promise.all([
      request({ url: '/api/images/get_all/', method: 'get' }),
      request({ url: '/api/dr/get_all/', method: 'get' }),
      request({ url: '/api/oct/get_all/', method: 'get' })
    ])

    // 处理不同来源的数据
    rawData.value = [
      ...mccRes.data.map((item: any) => ({ ...item, type: 'MCC' })),
      ...drRes.data.map((item: any) => ({ ...item, type: 'DR' })),
      ...octRes.data.map((item: any) => ({ ...item, type: 'OCT' }))
    ]
    processData()
  } finally {
    loading.value = false
  }
}
const typeTagStyle = (type: string) => {
  return {
    'MCC': 'primary',
    'DR': 'success',
    'OCT': 'warning'
  }[type] || 'info'
}
const processData = async() => {
  const groupMap = new Map<string, {
    type: 'MCC' | 'DR' | 'OCT'
    dirPath: string
    user:number
    username: string
    uploadTime: string
    total: number
    pairs: Map<string, { left: number | null; right: number | null }>
    singles: Set<number>
  }>()

  rawData.value.forEach(item => {
    if (!item.image_url.toLowerCase().endsWith('.jpg')) return
    const pathParts = item.image_url.split('\\')
    const dirPath = pathParts[1]
    const [username] = dirPath.split('-')
    const user=item.user
    const formatDateTime = (datetimeStr: string) => {
      const date = new Date(datetimeStr)
      return `${date.getFullYear()}-${(date.getMonth()+1).toString().padStart(2, '0')}-${
          date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${
          date.getMinutes().toString().padStart(2, '0')}`
    }
    const uploadTime = formatDateTime(item.create_datetime)
    const groupKey = `${item.type}_${dirPath}`
    if (!groupMap.has(groupKey)) {
      groupMap.set(groupKey, {
        type: item.type,
        dirPath,
        user,
        username,
        uploadTime,
        total: 0,
        pairs: new Map(),
        singles: new Set<number>()
      })
    }

    const group = groupMap.get(groupKey)!
    group.total++

    const fileName = item.image_url.split('\\').pop()!
    const match = fileName.match(/^(\d+)_(left|right)\.jpg$/i)

    if (match) {
      const [_, number, type] = match
      const pairKey = `${number}_pair`

      if (!group.pairs.has(pairKey)) {
        group.pairs.set(pairKey, { left: null, right: null })
      }

      const pair = group.pairs.get(pairKey)!
      type === 'left' ? pair.left = item.id : pair.right = item.id
    } else {
      group.singles.add(item.id)
    }
  })

  processedData.value = Array.from(groupMap.values()).map(group => {
    let validPairs = 0
    const pairDetails: PairDetail[] = []

    group.pairs.forEach((value, key) => {
      if (value.left && value.right) {
        validPairs++
        pairDetails.push({
          pairNumber: key.replace('_pair', ''),
          leftId: value.left,
          rightId: value.right,
          status: '已配对'
        })
      } else {
        const status = value.left ? '右图缺失' : '左图缺失'
        pairDetails.push({
          pairNumber: key.replace('_pair', ''),
          leftId: value.left || '-',
          rightId: value.right || '-',
          status: status as PairDetail['status']
        })
      }
    })

    group.singles.forEach(id => {
      pairDetails.push({
        pairNumber: '未配对',
        leftId: id,
        rightId: '-',
        status: '单张图片'
      })
    })
    return {
      type:group.type,
      dirPath: group.dirPath,
      user:group.user,
      username: group.username,
      uploadTime: group.uploadTime,
      total: group.total,
      validPairs,
      pairDetails
    }
  })
}

const statusTagType = (status: PairDetail['status']) => {
  const map = {
    '已配对': 'success',
    '左图缺失': 'warning',
    '右图缺失': 'warning',
    '单张图片': 'info'
  }
  return map[status] || 'info'
}

const showDetail = (row: ProcessedItem) => {
  currentUser.value = row.username
  detailData.value = row.pairDetails
  dialogVisible.value = true
}

const handleReturn = () => {
  router.push('/home')
}

const handleRowClick = (row: ProcessedItem, column: unknown, event: Event) => {
  if ((event.target as HTMLElement).closest('.el-button')) return
  const currentType = uploadTypes.find(t => t.value === row.type);
    const apiUrl = currentType?.info_api;
  router.push({
    path:apiUrl,
    query: { id: row.user,dirPath: row.dirPath }

  })
}
const fetchUserInfo = async (): Promise<UserInfo> => {
  try {
    const response = await request({
      url: '/api/system/user/user_info/',
      method: 'get'
    });
    return {
      role: response.data.role_info[0].key,
      username: response.data.username,
      name: response.data.name,
      id: response.data.id,
      identification: response.data.identification
    };
  } catch (error) {
    ElMessage.error('获取用户信息失败');
    throw error;
  }
};
const handleUploadClick = async () => {
  try {
    loading.value = true;
    const userInfo = await fetchUserInfo();
    (window as any).__tempUserInfo = userInfo;
    typeDialogVisible.value = true;
  } catch (error) {
    console.error('上传流程中断:', error);
  } finally {
    loading.value = false;
  }
};
const handleTypeConfirm = async () => {
  if (!selectedType.value) {
    ElMessage.warning('请选择上传类型');
    return;
  }
  typeDialogVisible.value = false;
  if (selectedType.value === 'MCC') {
    fileInput.value?.click(); // 触发包含Excel的文件选择
  } else {
    imageInput.value?.click(); // 触发纯图片选择
  }
};
const handleImageSelect = async (event: Event) => {
  const input = event.target as HTMLInputElement;
  const allFiles = Array.from(input.files || []);
  input.value = '';

  if (allFiles.length === 0) return;

  // 基础校验
  const invalidFiles = allFiles.filter(file =>
    !/\.jpe?g$/i.test(file.name) || file.size > 5 * 1024 * 1024
  );

  if (invalidFiles.length > 0) {
    ElMessage.warning(`发现${invalidFiles.length}个无效文件，已自动过滤`);
  }

  const validFiles = allFiles.filter(file =>
    /\.jpe?g$/i.test(file.name) && file.size <= 5 * 1024 * 1024
  );

  if (validFiles.length === 0) {
    ElMessage.error('未选取有效图片');
    return;
  }

  try {
    const userInfo = (window as any).__tempUserInfo;
    if (!userInfo?.id) {
      ElMessage.error('用户信息获取失败，请重试');
      return;
    }

    const formData = new FormData();
    formData.append('user', userInfo.id);
    validFiles.forEach(file => formData.append('file', file));

    const currentType = uploadTypes.find(t => t.value === selectedType.value);
    const loadingInstance = ElLoading.service({
      lock: true,
      text: `正在上传 ${validFiles.length} 个文件...`,
      background: 'rgba(0, 0, 0, 0.7)'
    });

    const response = await request({
      url: currentType?.api || '/api/dr/',
      method: 'post',
      data: formData,
      headers: {'Content-Type': 'multipart/form-data'}
    });

    loadingInstance.close();
    ElMessage.success(`成功上传 ${validFiles.length} 张图片`);
    await loadData();
  } catch (error) {
    ElMessage.error('上传失败');
    console.error('上传错误:', error);
  } finally {
    input.value = '';
  }
};
async function processUnpairedFiles(files: File[]): Promise<File[][]> {
  const processable = files.filter(file =>
    /^\d+_(left|right)\.jpe?g$/i.test(file.name)
  );

  const results: File[][] = [];
  for (const file of processable) {
    try {
      const mirror = await createMirrorFile(file);
      results.push([file, mirror]);
      ElMessage.success(`已生成镜像文件: ${mirror.name}`);
    } catch (error) {
      ElMessage.error(`处理失败: ${file.name}`);
      console.error('镜像生成错误:', error);
    }
  }
  return results;
}
function createMirrorFile(file: File): Promise<File> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      const canvas = document.createElement('canvas');
      canvas.width = img.width;
      canvas.height = img.height;
      const ctx = canvas.getContext('2d')!;
      ctx.translate(canvas.width, 0);
      ctx.scale(-1, 1);
      ctx.drawImage(img, 0, 0);

      canvas.toBlob(blob => {
        if (!blob) return reject(new Error('镜像生成失败'));

        const mirrorName = file.name
          .replace(/(left|right)/i, m =>
            m.toLowerCase() === 'left' ? 'right' : 'left'
          );

        resolve(new File([blob], mirrorName, { type: file.type }));
      }, 'image/jpeg', 0.9);
    };
    img.onerror = reject;
    img.src = URL.createObjectURL(file);
  });
}
const handleFileSelect = async (event: Event) => {
  const input = event.target as HTMLInputElement;
  const allFiles = Array.from(input.files || []);
  input.value='';
  if (allFiles.length === 0) return;
  const excelFiles = allFiles.filter(file =>
      file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
      file.name.toLowerCase().endsWith('.xlsx')
  );
  const imageFiles = allFiles.filter(file => !excelFiles.includes(file));
  if (excelFiles.length > 1) {
    ElMessage.warning('只能选择一个Excel文件');
    return;
  }
  // 获取之前存储的用户信息
  const userInfo = (window as any).__tempUserInfo;
  if (!userInfo?.id) {
    ElMessage.error('用户信息获取失败，请重试');
    return;
  }
  // 文件配对校验逻辑
  const fileMap = new Map<string, { left?: File; right?: File }>();
  const unpairedFiles: File[] = [];

  // 第一步：分类校验
  imageFiles.forEach(file => {
    // 基础校验：格式和大小
    if (!/\.jpe?g$/i.test(file.name) || file.size > 5 * 1024 * 1024) {
      unpairedFiles.push(file);
      return;
    }

    // 高级校验：配对规则
    const match = file.name.match(/^(\d+)_(left|right)\.jpe?g$/i);
    if (!match) {
      unpairedFiles.push(file);
      return;
    }

    const [, number, side] = match;
    const key = number.toLowerCase();
    const sideType = side.toLowerCase() as 'left' | 'right';

    if (!fileMap.has(key)) {
      fileMap.set(key, {});
    }

    const pair = fileMap.get(key)!;
    if (pair[sideType]) {
      unpairedFiles.push(file); // 重复方向文件
    } else {
      pair[sideType] = file;
    }
  });

  // 第二步：筛选有效配对
  const validFiles: File[] = [];
  fileMap.forEach((pair, number) => {
    if (pair.left && pair.right) {
      validFiles.push(pair.left, pair.right);
    } else {
      if (pair.left) unpairedFiles.push(pair.left);
      if (pair.right) unpairedFiles.push(pair.right);
    }
  });

  // 未配对文件提示
  if (unpairedFiles.length > 0) {

    input.value='';
    try {
      // 弹出处理对话框
      const action = await ElMessageBox({
        title: '发现未配对图片',
        message: `
          <div style="margin-bottom: 15px;">
            发现${unpairedFiles.length}个单张图片，请选择处理方式：
          </div>
        `,
        showClose: false,
        showCancelButton: true,
        confirmButtonText: '镜像处理',
        cancelButtonText: '重新选择',
        distinguishCancelAndClose: true,
        dangerouslyUseHTMLString: true,
        closeOnClickModal: false,
        type: 'warning'
      }).catch(action => action === 'cancel' ? 'rechoose' : 'cancel')

      if (action === 'rechoose') {
        input.value = '';
        fileInput.value?.click();
        return;
      }
      if (action === 'cancel') {
        input.value = '';
        return;
      }
      // 镜像处理逻辑
      const processedPairs = await processUnpairedFiles(unpairedFiles);
      validFiles.push(...processedPairs.flat());
    } catch (error) {
      console.error('镜像处理失败:', error);
      return;
    }
  }
  // 无有效文件时中断
  if (validFiles.length === 0) {
    ElMessage.error('未选取图片');
    return;
  }
  try {
    const formData = new FormData();
    formData.append('user', userInfo.id);
    validFiles.forEach(file => {
      formData.append('file', file); // 关键点：使用相同字段名多次append
    });
    if (excelFiles.length === 1) {
      //验证
      const excelFile = excelFiles[0];
      const pairedImages = Array.from(fileMap.values())
          .filter(pair => pair.left && pair.right)
          .map(pair => ({
            left: pair.left!.name,
            right: pair.right!.name
          }));

      // 执行验证
      const { missingInExcel, missingInImages } = await validateExcelMatches(excelFile, pairedImages);

      // 生成差异提示
      const messages: string[] = [];

      if (missingInExcel.length > 0) {
        const example = missingInExcel.slice(0, 3).join('; ');
        messages.push(
            `${missingInExcel.length} 对图片在Excel中缺失：${example}${missingInExcel.length > 3 ? '...' : ''}`
        );
      }

      if (missingInImages.length > 0) {
        const example = missingInImages.slice(0, 3).join('; ');
        messages.push(
            `${missingInImages.length} 对Excel记录缺少图片：${example}${missingInImages.length > 3 ? '...' : ''}`
        );
      }
      if (messages.length > 0) {
        await ElMessageBox.confirm(
            `发现匹配问题：\n${messages.join('\n')}\n\n是否继续上传？`,
            '文件不匹配警告',
            {
              confirmButtonText: '继续上传',
              cancelButtonText: '取消上传',
              type: 'warning'
            }
        );
      }
      formData.append('excel',excelFiles[0]);
    }
    else {
      const excelFile = await triggerExcelSelect();
      const pairedImages = Array.from(fileMap.values())
          .filter(pair => pair.left && pair.right)
          .map(pair => ({
            left: pair.left!.name,
            right: pair.right!.name
          }));

      // 执行验证
      const { missingInExcel, missingInImages } = await validateExcelMatches(excelFile, pairedImages);

      // 生成差异提示
      const messages: string[] = [];

      if (missingInExcel.length > 0) {
        const example = missingInExcel.slice(0, 3).join('; ');
        messages.push(
            `${missingInExcel.length} 对图片在Excel中缺失：${example}${missingInExcel.length > 3 ? '...' : ''}`
        );
      }

      if (missingInImages.length > 0) {
        const example = missingInImages.slice(0, 3).join('; ');
        messages.push(
            `${missingInImages.length} 对Excel记录缺少图片：${example}${missingInImages.length > 3 ? '...' : ''}`
        );
      }
      if (messages.length > 0) {
        await ElMessageBox.confirm(
            `发现匹配问题：\n${messages.join('\n')}\n\n是否继续上传？`,
            '文件不匹配警告',
            {
              confirmButtonText: '继续上传',
              cancelButtonText: '取消上传',
              type: 'warning'
            }
        );
      }
      formData.append('excel', excelFile);
    }
    const loadingInstance = ElLoading.service({
      lock: true,
      text: `正在上传 ${validFiles.length} 个配对文件...`,
      background: 'rgba(0, 0, 0, 0.7)'
    });
    const apiUrl = '/api/images/';
    const response = await request({
      url: apiUrl,
      method: 'post',
      data: formData,
      headers: {
        'Content-Type': 'multipart/form-data'
      }
    });
    loadingInstance.close();
    const excelUrl = response.data[0].excel;
    const url = new URL(excelUrl);
    const fullPath = decodeURIComponent(url.pathname.substring(1)); // 解码并去掉开头的 '/'

    // 3. 构建本地路径（替换斜杠为 Windows 反斜杠）
    const localFilePath =fullPath.replace(/\//g, '\\');       // 文件完整路径
    const localDirPath =fullPath.replace(/\/[^/]+$/, '').replace(/\//g, '\\'); // 目录路径
    const postData = new FormData();
    postData.append('image_url', localDirPath)
    postData.append('excelname', localFilePath)
    console.log(localDirPath)
    console.log(localFilePath)
    // await request({
    //   url: '/api/images/prediction',
    //   method: 'post',
    //   data: postData,
    //   headers: {
    //     'Content-Type': 'multipart/form-data'
    //   }
    // })
    ElMessage.success(`成功上传 ${validFiles.length/2} 对图片`);
    input.value = '';
    await loadData();
  } catch (error) {
    if (input) input.value = '';
    ElMessage.error('上传失败');
    console.error('上传错误:', error);
  }
};
const handleExcelSelect = async (event: Event) => {
  const input = event.target as HTMLInputElement;
  const files = Array.from(input.files || []);
  input.value = '';
  try {
    const file = files[0];
    const isExcel = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' ||
        file.name.toLowerCase().endsWith('.xlsx');
    if (!isExcel) {
      throw new Error('无效的Excel文件');
    }
    if (excelPromiseResolve.value) {
      excelPromiseResolve.value(file);
    }
  } catch (error) {
    if (excelPromiseReject.value) {
      excelPromiseReject.value(error);
    }
    if (error instanceof Error) {
      ElMessage.error(error.message);
    }
  } finally {
    // 清空回调引用
    excelPromiseResolve.value = null;
    excelPromiseReject.value = null;
  }
};
const triggerExcelSelect = (): Promise<File> => {
  return new Promise((resolve, reject) => {
    // 保存回调函数
    excelPromiseResolve.value = resolve;
    excelPromiseReject.value = reject;

    // 触发文件选择
    excelInput.value?.click();
  });
};
const validateExcelMatches = async (
    excelFile: File,
    pairedImages: Array<{ left: string; right: string }>
): Promise<{
  excelPairs: Array<{ left: string; right: string }>;
  missingInExcel: string[];
  missingInImages: string[];
}> => {
  return new Promise((resolve, reject) => {
    const reader = new FileReader();

    reader.onload = (e) => {
      try {
        const data = new Uint8Array(e.target?.result as ArrayBuffer);
        const workbook = XLSX.read(data, { type: 'array' });
        const worksheet = workbook.Sheets[workbook.SheetNames[0]];
        const jsonData = XLSX.utils.sheet_to_json(worksheet, { header: 1 });

        // 验证列标题
        const header = jsonData[0] as string[];
        const leftColIndex = header.findIndex(h => h?.toString().trim() === 'Left-Fundus');
        const rightColIndex = header.findIndex(h => h?.toString().trim() === 'Right-Fundus');
        // 获取Excel中所有有效配对
        const excelPairs = jsonData.slice(1).reduce((acc: Array<{ left: string; right: string }>, row) => {
          const left = (row as any[])[leftColIndex]?.toString().trim();
          const right = (row as any[])[rightColIndex]?.toString().trim();
          if (left && right) acc.push({ left, right });
          return acc;
        }, []);

        // 转换为Set用于快速查找
        const imageSet = new Set(pairedImages.map(p => `${p.left}|${p.right}`));
        const excelSet = new Set(excelPairs.map(p => `${p.left}|${p.right}`));

        // 找出差异
        const missingInExcel = pairedImages
            .filter(p => !excelSet.has(`${p.left}|${p.right}`))
            .map(p => `${p.left} / ${p.right}`);

        const missingInImages = excelPairs
            .filter(p => !imageSet.has(`${p.left}|${p.right}`))
            .map(p => `${p.left} / ${p.right}`);

        resolve({ excelPairs, missingInExcel, missingInImages });
      } catch (error) {
        reject(error);
      }
    };

    reader.onerror = () => reject(new Error('读取Excel文件失败'));
    reader.readAsArrayBuffer(excelFile);
  });
};
</script>

<style scoped>
.container {
  padding: 32px 24px; /* 减少两侧留白 */
  background: #fff;
  border-radius: 16px;
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
  position: relative;
  min-height: calc(100vh - 180px);
  max-width: 930px; /* 增加最大宽度 */
  margin: 0 auto;
  overflow-y: hidden;
  overflow-x: hidden;

}

/* 表格容器 */
.history-table {
  overflow-y: hidden;
  overflow-x: hidden;
  border-collapse: separate;
  border-spacing: 0 16px; /* 增加行间距 */
  width: 100%;
  background: transparent;
  margin: 24px 0;
}

.history-table :deep(.el-table__header) {
  background: linear-gradient(135deg, #f8f9fe 0%, #f3f4f9 100%);
}

.history-table :deep(.el-table__header th) {
  font-size: 16px !important; /* 增大表头字号 */
  height: 64px;
}

.history-table :deep(.el-table__row) {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  cursor: pointer;
  background: #fff;
  margin-bottom: 16px; /* 增加行下边距 */
  border-radius: 12px !important;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); /* 加强阴影效果 */

}

.history-table :deep(.el-table__row:hover) {
  transform: translateY(-3px);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

.history-table :deep(.el-table__cell) {
  padding: 20px 0 !important; /* 增加单元格垂直内距 */
  font-size: 15px !important; /* 增大内容字号 */
}

/* 列宽调整 */
.history-table :deep(.el-table__column) {
  min-width: 140px; /* 设置最小列宽 */
}

/* 状态标签增强 */
.status-tag {
  font-weight: 700 !important;
  min-width: 100px;
  padding: 10px 16px !important;
  border-width: 2px !important;
}

/* 详情对话框优化 */
.detail-dialog {
  width: 72% !important; /* 增大对话框宽度 */
}

.detail-dialog :deep(.el-table__cell) {
  padding: 16px 0 !important;
}


/* 斑马条纹增强 */
.history-table :deep(.el-table__row--striped) {
  background: #fafbff !important;
}

/* 计数徽章增强 */
.count-badge {
  padding: 10px 20px;
  font-size: 15px;
  border-width: 2px;
}
.history-container {
  font-size: 1rem;
  padding: 2rem;
  background: #f8fafc;
  min-height: 100vh;
  max-height: 90vh;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.top-bar {
  display: flex;
  flex-wrap: wrap; /* 允许换行 */
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
  padding: 1.5rem 3rem;
  background: white;
  border-radius: 1rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
  flex-shrink: 0;

}
.top-bar {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
  padding: 1.5rem 3rem;
  background: white;
  border-radius: 1rem;
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.05);
  flex-shrink: 0;
}

.app-title {
  font-size: 3rem;
  color: #1e293b;
  font-weight: 600;
  letter-spacing: -0.025em;
}

.action-container {
  display: flex;
  align-items: center;
  gap: 2rem;
}

.action-buttons {
  display: flex;
  gap: 1.5rem;
}

.search-group {
  display: flex;
  gap: 10px;
}

.search-input {
  width: 200px;
  transition: width 0.3s;
}

.search-input:focus-within {
  width: 250px;
}

.flat-button {
  border: none !important;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1) !important;
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1) !important;
  background: dodgerblue !important;
  width: 48px;
  height: 48px;

  &:hover {
    transform: scale(1.2)  !important;
    box-shadow: 0 6px 16px rgba(0, 0, 0, 0.2) !important;
    background: #1a73e8 !important;
  }

  &.el-button--primary {
    color: #409eff !important;
    &:hover { background: #1a73e8 !important; }
  }

  &.el-button--info {
    color: #909399 !important;
    &:hover { background: #909399 !important; }
  }
}
.return-button {
  border: none !important;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1) !important;
  transition: all 0.3s ease !important;
  background: white !important;
  width: 48px;
  height: 48px;

  &:hover {
    transform: scale(1.2) !important;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15) !important;
  }

  &.el-button--primary {
    color: #409eff !important;
    &:hover { background: #e8f4ff !important; }
  }

  &.el-button--info {
    color: #909399 !important;
    &:hover { background: #f4f4f5 !important; }
  }
}
.search-group {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}

.date-picker {
  width: 240px;
}

.number-range {
  display: flex;
  align-items: center;
  gap: 8px;
}

.number-input {
  width: 120px;
}

.separator {
  color: #999;
  padding: 0 4px;
}

.search-button {
  height: 40px;
  padding: 0 24px;
  transition: all 0.3s;
}

.search-button:hover {
  transform: translateY(-2px);
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}

/* 响应式调整 */
.type-dialog {
  :deep(.el-dialog__body) {
    padding: 20px;
  }
}

.type-selector {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 20px;
  margin: 20px 0;
}

.type-card {
  padding: 24px;
  border: 2px solid #ebeef5;
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.3s ease;
  text-align: center;
  background: white;

  &:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
    border-color: #409EFF;
  }
}

.active-type {
  border-color: #409EFF;
  background: #f5f7fa;

  .type-title {
    color: #409EFF;
  }
}

.type-title {
  margin: 12px 0 8px;
  font-size: 16px;
  color: #606266;
  transition: color 0.3s;
}

.type-desc {
  font-size: 12px;
  color: #909399;
  line-height: 1.4;
  margin: 0;
}
.filter-buttons {
  display: flex;
  gap: 0;
  margin-bottom: -1px;
  position: relative;
  z-index: 2;
  background: #f5f7fa;
  border-radius: 8px 8px 0 0;
  overflow: hidden;

  .type-btn {
    flex: 1 1 0%;
    padding: 14px 0;
    border-radius: 0;
    border: none;
    background: #f5f7fa;
    color: #606266;
    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
    position: relative;
    transform-origin: center;
    white-space: nowrap;

    &::after {
      content: '';
      position: absolute;
      right: 0;
      top: 50%;
      transform: translateY(-50%);
      width: 1px;
      height: 16px;
      background: #dcdfe6;
    }

    &:last-child::after {
      display: none;
    }

    &:hover {
      flex: 1.2 1 0%;
      background: #409EFF;
      color: #ffffff;
      z-index: 2;
      transform: scaleX(1.05);
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
    }

    &.active {
      flex: 1.2 1 0%;
      background: #409EFF;
      color: #ffffff;
      z-index: 3;
      transform: scaleX(1.05);
      box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
    }

    &.active:hover {
      flex: 1.25 1 0%;
      transform: scaleX(1.08);
    }
  }
}
</style>